﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using SuperSerialization.Tree;

namespace SuperSerialization
{
    /// <summary>
    /// Constructs a document from a text reader.
    /// </summary>
    class Parser
    {
        /// <summary>
        /// The tokenizer in use.
        /// </summary>
        protected Tokenizer tokenizer;

        /// <summary>
        /// Constructs a parser for the given reader.
        /// </summary>
        /// <param name="reader">A reader containing contents to parse.</param>
        public Parser(TextReader reader)
        {
            tokenizer = new Tokenizer(reader);
        }

        /// <summary>
        /// Parses the contents of the reader.
        /// </summary>
        /// <returns>A document tree.</returns>
        public Node Parse()
        {
            return ParseBase();
        }

        /// <summary>
        /// Rule for the base container.
        /// Namespace* Object
        /// </summary>
        /// <returns>A base node.</returns>
        protected BaseNode ParseBase()
        {
            List<Node> values = new List<Node>();
            while (SeeNext(TokenType.NAMESPACE))
            {
                NamespaceNode ns = ParseNamespace();
                if (ns != null)
                {
                    values.Add(ns);
                }
            }

            values.Add(ParseObject());

            return new BaseNode(values.ToArray());
        }

        /// <summary>
        /// Rule for parsing a namespace usage statement. Requires that the next token be a namespace.
        /// Namespace Identifier ;
        /// </summary>
        /// <returns>A namespace node.</returns>
        protected NamespaceNode ParseNamespace()
        {
            Expect(TokenType.NAMESPACE);
            Token identifier = Expect(TokenType.IDENTIFIER);
            if (SeeNext(TokenType.SEMICOLON)) Next();
            return new NamespaceNode(identifier.Value);
        }

        /// <summary>
        /// Rule for attempting to parse an object.
        /// Identifier | Identifier { Fields }
        /// </summary>
        /// <returns>An object node if there was an object, or null if the next element is not an object.</returns>
        protected ObjectNode ParseObject()
        {
            if (!SeeNext(TokenType.IDENTIFIER)) return null;
            Token typeIdentifier = Expect(TokenType.IDENTIFIER);
            List<FieldNode> fields = new List<FieldNode>();
            if (SeeNext(TokenType.OPEN_BRACE))
            {
                Expect(TokenType.OPEN_BRACE);
                fields = ParseFields();
                Expect(TokenType.CLOSE_BRACE);
            }
            return new ObjectNode(typeIdentifier.Value, fields);
        }

        /// <summary>
        /// Parses the field assignments of an object.
        /// Field Fields
        /// </summary>
        /// <returns>A list of field nodes.</returns>
        protected List<FieldNode> ParseFields()
        {
            List<FieldNode> fields = new List<FieldNode>();
            FieldNode field = ParseField();
            if (field != null)
            {
                fields.Add(field);
                fields.AddRange(ParseFields());
            }
            return fields;
        }

        /// <summary>
        /// Attempts to parse a single field assignment.
        /// Identifier : Value ;
        /// </summary>
        /// <returns>A field node or null if there is not another field.</returns>
        protected FieldNode ParseField()
        {
            if (!SeeNext(TokenType.IDENTIFIER)) return null;
            Token id = Next();
            Expect(TokenType.ASSIGNMENT);
            Node value = ParseValue();
            if (value == null) Error("Assignment to " + id.Value + " without valid value.");
            if (SeeNext(TokenType.SEMICOLON)) Next();
            return new FieldNode(id.Value, value);
        }

        /// <summary>
        /// Parses the value of a field assignment.
        /// Array | Literal | Object
        /// </summary>
        /// <returns>A node representing the value.</returns>
        protected Node ParseValue()
        {
            Node value = ParseArray();
            if(value == null)
                value = ParseLiteral();
            if (value == null)
                value = ParseObject();
            return value;
        }

        /// <summary>
        /// Parses a comma separated list.
        /// Value, Values | Value
        /// </summary>
        /// <returns>A list of nodes.</returns>
        protected List<Node> ParseValues()
        {
            List<Node> values = new List<Node>();
            Node value = ParseValue();
            if (value != null)
            {
                values.Add(value);
                if (SeeNext(TokenType.SEPARATOR))
                {
                    Next();
                    List<Node> rValues = ParseValues();
                    if (rValues.Count == 0)
                        Error("Unexpected ',' after list element.");
                    values.AddRange(rValues);
                }
            }
            return values;
        }

        /// <summary>
        /// Parses an array.
        /// [ Values ]
        /// </summary>
        /// <returns></returns>
        protected ArrayNode ParseArray()
        {
            if (!SeeNext(TokenType.OPEN_SQUARE)) return null;
            Expect(TokenType.OPEN_SQUARE);
            List<Node> values = ParseValues();
            Expect(TokenType.CLOSE_SQUARE);
            return new ArrayNode(values.ToArray());
        }

        /// <summary>
        /// Parses a literal.
        /// </summary>
        /// <returns>A literal node, or null if one was not found.</returns>
        protected Node ParseLiteral()
        {
            if (!Peek().IsLiteral && !SeeNext(TokenType.MINUS)) return null;
            if (Peek().IsNumericLiteral || SeeNext(TokenType.MINUS))
                return ParseNumber();
            if (SeeNext(TokenType.TRUE))
            {
                Next();
                return new LiteralNode(true);
            }
            if (SeeNext(TokenType.FALSE))
            {
                Next();
                return new LiteralNode(false);
            }
            if (SeeNext(TokenType.NULL))
            {
                Next();
                return new LiteralNode(null);
            }
            return new LiteralNode(Next().Value);
        }

        /// <summary>
        /// Parses a number.
        /// Minus Number | Number
        /// Throws ParseException if not a number.
        /// </summary>
        /// <returns>A literal node.</returns>
        protected Node ParseNumber()
        {
            string sign = "";
            if (SeeNext(TokenType.MINUS))
            {
                sign = "-";
                Next();
            }
            if (SeeNext(TokenType.INTEGER))
            {
                Token number = Next();
                return new LiteralNode(Convert.ToInt32(sign + number.Value));
            }
            else if (SeeNext(TokenType.FLOAT))
            {
                Token number = Next();
                return new LiteralNode((float)Convert.ToDouble(sign + number.Value));
            }
            else if (SeeNext(TokenType.DOUBLE))
            {
                Token number = Next();
                return new LiteralNode(Convert.ToDouble(sign + number.Value));
            }
            throw new ParseException("Expected: Number; Got: " + Peek().Type);
        }

        /// <summary>
        /// Checks if the next token is a given type.
        /// </summary>
        /// <param name="type">The token type to check for.</param>
        /// <returns>True if the next token is the given type. False otherwise.</returns>
        protected bool SeeNext(TokenType type)
        {
            return tokenizer.HasNext() && tokenizer.Peek().Type == type;
        }

        /// <summary>
        /// Peeks at the next token and returns it.
        /// </summary>
        /// <returns>The next token.</returns>
        protected Token Peek()
        {
            return tokenizer.Peek();
        }

        /// <summary>
        /// Reads the next token and increments the tokenizer to the next place.
        /// </summary>
        /// <returns>The next token.</returns>
        protected Token Next()
        {
            return tokenizer.Next();
        }

        /// <summary>
        /// Throws an exception with the given message in addition to a line number.
        /// </summary>
        /// <param name="message">The message to put in the exception.</param>
        protected void Error(string message)
        {
            throw new ParseException("(Line " + tokenizer.Line + ") " + message);
        }

        /// <summary>
        /// Generates an exception indicating a difference between the token received and the token expected.
        /// </summary>
        /// <param name="got">The token that was received.</param>
        /// <param name="expected">The token that was expected.</param>
        protected void Error(Token got, TokenType expected)
        {
            Error("Expected: " + expected + "; Got: " + got.ToString());
        }

        /// <summary>
        /// Requires that the next token be a given type. If it is the given type, the token is popped off and returned.
        /// </summary>
        /// <param name="type">The type to require.</param>
        /// <returns>The next token.</returns>
        protected Token Expect(TokenType type)
        {
            Token token = Next();
            if (token.Type == type) return token;
            Error(token, type);
            return null;
        }
    }

    /// <summary>
    /// An exception indicating an error occurred during parsing.
    /// </summary>
    public class ParseException : Exception
    {
        /// <summary>
        /// Constructs a new parse exception.
        /// </summary>
        /// <param name="message">The message to put in the exception.</param>
        public ParseException(string message) : base("Parser Error: " + message)
        {

        }
    }
}
